import UIKit

// 游戏回调
public protocol GameCallback: NSObjectProtocol {
    func onGameOver()   // 游戏结束事件
}

// 游戏
class Game: NSObject, PictureCtrlCallback {
    // 难度
    public enum Difficulty: Int {
        case EASY = 9       // 简单
        case MEDIUM = 16    // 中等
        case HARD = 25      // 困难
    }

    // 状态
    public enum Status {
        case END        // 已结束
        case PLAYING    // 游戏中
    }

    private let mCallback: GameCallback         // 回调
    private var mDifficulty: Difficulty         // 难度
    private let mDrawer: CGContext              // 绘图者
    private let mEmptyAreaRect: CGRect          // 空白区域矩形
    private var mEmptyPoint: CGPoint!           // 空点，即没有图的那个点
    private let mGameBoard: UIView              // 游戏区域
    private let mImage: CGContext               // 图片
    private var mPicHasBorder: Bool             // 图片是否有边框标志
    private var mPictures: Array<PictureCtrl>!  // 图片控件数组
    private var mStatus: Status                 // 状态

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public init(_ callback: GameCallback, drawer: CGContext, gameBoard: UIView) {
        // 初始化成员
        mCallback = callback
        mDifficulty = .EASY
        mDrawer = drawer
        mGameBoard = gameBoard
        mImage = CGContext(data: nil, width: Int(gameBoard.frame.width), height: Int(gameBoard.frame.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: 1)!
        mPicHasBorder = true
        mStatus = .END

        // 设置空白区域的位置和尺寸
        let gameBoardWidth = mGameBoard.frame.width
        let gameBoardHeight = mGameBoard.frame.height
        let diffBaseEasy = CGFloat(sqrtf(Float(Difficulty.EASY.rawValue)))
        let diffBaseMedium = CGFloat(sqrtf(Float(Difficulty.MEDIUM.rawValue)))
        let diffBaseHard = CGFloat(sqrt(Float(Difficulty.HARD.rawValue)))
        var emptyAreaSize = gameBoardHeight
        repeat {
            emptyAreaSize -= 1
        } while emptyAreaSize.truncatingRemainder(dividingBy: diffBaseEasy) != 0 ||
            emptyAreaSize.truncatingRemainder(dividingBy: diffBaseMedium) != 0 ||
            emptyAreaSize.truncatingRemainder(dividingBy: diffBaseHard) != 0
        let emptyAreaLeft = (gameBoardWidth - emptyAreaSize) / 2
        let emptyAreaTop = (gameBoardHeight - emptyAreaSize) / 2
        mEmptyAreaRect = CGRect(x: emptyAreaLeft, y: emptyAreaTop, width: emptyAreaSize, height: emptyAreaSize)

        super.init()
        
        // 设置图片
        let image = UIImage(named: Const.PICTURE_FILE_NAME)!.cgImage!
        self.setPicture(image)
    }

    /**
     * 创建所有图片控件。
     */
    private func createPictures() {
        var left: CGFloat, top: CGFloat                         // 图片控件位置
        let baseCount = Int(sqrtf(Float(mDifficulty.rawValue))) // 一行（列）的图片数
        let size = mEmptyAreaRect.width / CGFloat(baseCount)    // 图片控件尺寸
        var rect: CGRect                                        // 图片控件位置、尺寸
        var j = 0                                               // 换行标志，0为第一行，1为第二行，依次类推

        // 删除所有图片控件
        self.deletePictures()

        // 创建所有图片控件
        mPictures = Array<PictureCtrl>()
        let image = mImage.makeImage()!
        for i in 0 ..< mDifficulty.rawValue {
            if i % baseCount == 0 && i != 0 {   // 如果一行满了，换到下一行
                j += 1
            }

            // 计算左上角坐标
            left = mEmptyAreaRect.minX + size * CGFloat(i % baseCount)
            top = mEmptyAreaRect.minY + size * CGFloat(j)

            // 设置位置、尺寸
            rect = CGRect(origin: CGPoint(x: left, y: top), size: CGSize(width: size, height: size))
            
            // 创建图片控件
            let pictureCtrl = PictureCtrl(self, hasBorder: mPicHasBorder, image: image, parent: mGameBoard, rect: rect)
            mPictures.append(pictureCtrl)
        }
    }

    /**
     * 删除所有图片控件。
     */
    private func deletePictures() {
        if mPictures == nil {
            return
        }
        
        for pictureCtrl in mGameBoard.subviews {
            pictureCtrl.removeFromSuperview()
        }
        mPictures.removeAll()
    }

    /**
     * 获取/设置游戏难度。
     */
    public var difficulty: Difficulty {
        get {
            return mDifficulty
        }

        set {
            // 设置难度
            mDifficulty = newValue

            // 重新创建所有图片
            self.createPictures()

            // 将状态设置为已结束
            mStatus = Status.END
        }
    }

    /**
    * 游戏结束。
    */
    private func gameOver() {
        // 空白图片框显示图片
        mPictures[mDifficulty.rawValue - 1].isHidden = false
        
        // 将状态设置为已结束
        mStatus = .END

        // 发送通知
        mCallback.onGameOver()
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private func isWon() -> Bool {
        var result = false
        for i in 0 ..< mDifficulty.rawValue {
            result = mPictures[i].isOnOriginalPosition()
            if !result {
                break
            }
        }
        return result
    }

    /**
     * 移动图片控件。
     * @param pictureCtrl   图片控件
     */
    internal func movePicture(_ pictureCtrl: PictureCtrl) {
        if mStatus != Status.PLAYING {
            return
        }

        let position = CGPoint(x: pictureCtrl.frame.origin.x, y: pictureCtrl.frame.origin.y)

        // 如果要移动的图片控件与空点相邻，则移动
        if position.y == mEmptyPoint.y && abs(position.x - mEmptyPoint.x) == pictureCtrl.frame.width ||
            position.x == mEmptyPoint.x && abs(position.y - mEmptyPoint.y) == pictureCtrl.frame.height {
            pictureCtrl.frame.origin = mEmptyPoint
            mEmptyPoint = position
            
            // 判断是否胜利
            if self.isWon() {
                self.gameOver()
            }
        }
    }

    /**
     * 获取/设置图片控件是否有边框。
     */
    public var pictureHasBorder: Bool {
        get {
            return mPicHasBorder
        }

        set {
            mPicHasBorder = newValue
            
            // 设置所有图片控件的边框
            for picture in mPictures {
                picture.hasBorder = newValue
            }
        }
    }

    /**
     * 设置图片。
     * @param image 图片
     */
    public func setPicture(_ image: CGImage) {
        // 将图片画到游戏区域上
        mImage.draw(image, in: mGameBoard.frame)
        mDrawer.saveGState()
        mDrawer.translateBy(x: 0, y: mGameBoard.frame.height)
        mDrawer.scaleBy(x: 1.0, y: -1.0)
        mDrawer.draw(image, in: mGameBoard.frame)
        mDrawer.restoreGState()
        
        // 画中间的空白区域
        mDrawer.setFillColor(Const.EMPTY_AREA_COLOR)
        mDrawer.fill(mEmptyAreaRect)
        
        // 通知游戏区域重绘
        mGameBoard.setNeedsDisplay()
        
        // 创建所有图片
        self.createPictures()
        
        // 将状态设置为已结束
        mStatus = Status.END
    }

    /**
     * 游戏开始。
     */
    public func start() {
        // 重新创建所有图片
        if mStatus != Status.END {
            self.createPictures()
        }

        // 重置成员变量
        mEmptyPoint = CGPoint(x: mPictures[mDifficulty.rawValue - 1].frame.origin.x, 
            y: mPictures[mDifficulty.rawValue - 1].frame.origin.y)
        mStatus = Status.PLAYING

        // 隐藏右下角图片控件
        mPictures[mDifficulty.rawValue - 1].isHidden = true

        // 乱序其它图片控件的位置
        var index1: Int, index2: Int
        for _ in 0 ..< mDifficulty.rawValue - 1 {
            repeat {
                index1 = Int(arc4random_uniform(UInt32(mDifficulty.rawValue - 1)))
                index2 = Int(arc4random_uniform(UInt32(mDifficulty.rawValue - 1)))
            } while index1 == index2
            self.swapPictures(index1, index2: index2)
        }
    }
    
    /**
     * 获取游戏状态。
     */
    public var status: Status {
        get {
            return mStatus
        }
    }

    /**
     * 交换两个图片控件的位置。
     * @param index1    图片控件1的索引
     * @param index2    图片控件2的索引
     */
    private func swapPictures(_ index1: Int, index2: Int) {
        let position1 = CGPoint(x: mPictures[index1].frame.origin.x, y: mPictures[index1].frame.origin.y)
        let position2 = CGPoint(x: mPictures[index2].frame.origin.x, y: mPictures[index2].frame.origin.y)
        mPictures[index1].frame.origin = position2
        mPictures[index2].frame.origin = position1
    }
}
